package futbol.tacticas.concursantes.Silgur;
import futbol.tacticas.concursantes.Tactica;
import futbol.tacticas.*;
import java.awt.Color;
import java.awt.Point;
import java.util.Vector;

public class Silgur implements Tactica 
{
    // Players definition variables.
    //      [#0, #1] -> Initial player position
    //      [#2]     -> Power
    //      [#3]     -> Row number    
    //      [#4]     -> Linked player (for defensive/attack tactic)
    private int nPlayers1[][] =
    {
        // Goal Kepper
        {-148,    0,    5,  0,  -1},      // #0
        
        // First row: three defensive players
        {-115,  -30,    6,  1,  -1},      // #1
        {-111,    0,    6,  1,  -1},      // #2
        {-115,   30,    6,  1,  -1},      // #3
        
        // Second row: two defensive players
        { -30,  -80,    6,  2,  -1},      // #4
        { -30,   80,    6,  2,  -1},      // #5
        
        // Third row: three attacker players
        {  40,  -75,    7,  3,  -1},      // #6
        {  43,    0,    7,  3,  -1},      // #7
        {  40,   75,    7,  3,  -1},      // #8

        // Fourth row: two attacker players
        { 120,  -35,    7,  4,  -1},      // #9
        { 120,   35,    7,  4,  -1},      // #10
    };
    
    private int nPlayers2[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
    
    // Players positions
    private Point[] ptTeamPlayers1 = new Point[11];
    private Point[] ptTeamPlayers2 = new Point[11];
    
    // Current match result
    private int nMatchResult[] = { 0, 0 };
    
    // Ball historic values
    private Point[] ptBalls = new Point[3];
    private int nBallStartIdx = -1;
    private int nValidBalls = 0;
    
    // Logging value
    private boolean bLog = true;
    
    // Calculate the distance between to given points
    private double Distance(Point pt1, Point pt2)
    {
        return VectorLength(pt1.getX() - pt2.getX(), pt1.getY() - pt2.getY());
    }
    
    // Calculate the distance between any player and a given point
    private double Distance(int nPlayer, Point pt, boolean bAttacker)
    {
        double      nDistance;
        nDistance = bAttacker ? Distance(ptTeamPlayers2[nPlayer], pt) :
                                Distance(ptTeamPlayers1[nPlayer], pt);
        return nDistance;
    }
    
    // Calculate the distance between two players
    private double Distance(int nPlayer1, int nPlayer2, boolean bAttacker)
    {
        double      nDistance;
        nDistance = bAttacker ? Distance(ptTeamPlayers1[nPlayer1], ptTeamPlayers2[nPlayer2]) :
                                Distance(ptTeamPlayers1[nPlayer1], ptTeamPlayers1[nPlayer2]);
        return nDistance;
    }
    
    // Calculate the length for a given vector values
    private double VectorLength(double nX, double nY)
    {
        double      nLength;
        nLength  = Math.sqrt((nX * nX) + (nY * nY));
        return nLength;        
    }
    
    // Interpolate two points using a given factor
    private Point Interpolate(Point pt1, Point pt2, double nFactor)
    {
        Point       ptResult = new Point(0, 0);
        double      nInvFactor;
        nInvFactor = 1.0 - nFactor;
        ptResult.setLocation(nFactor * pt1.getX() + nInvFactor * pt2.getX(),
                             nFactor * pt1.getY() + nInvFactor * pt2.getY());
        return ptResult;
    }
    
    // Given a two reference points, this functions calculates another point
    // that lies on the segment defined by the two points and using another
    // given X value.
    private Point CalculatePointFromX(Point ptRef1, Point ptRef2, double nXValue)
    {
        Point       ptResult = new Point(0, 0);
        Point       ptDiff = new Point(0, 0);
        double      nSlope;
        ptDiff.setLocation(ptRef2.getX() - ptRef1.getX(), ptRef2.getY() - ptRef1.getY());        
        nSlope = ptDiff.getY() / ptDiff.getX();        
        ptResult.setLocation(nXValue, ptRef1.getY() + ((nXValue - ptRef1.getX())) * nSlope);
        return ptResult;                              
    }
    
    // Given a two reference points, this functions calculates another point
    // that lies on the segment defined by the two points and using another
    // given Y value.
    private Point CalculatePointFromY(Point ptRef1, Point ptRef2, double nYValue)
    {
        Point       ptResult = new Point(0, 0);
        Point       ptDiff = new Point(0, 0);
        double      nSlope;
        ptDiff.setLocation(ptRef2.getX() - ptRef1.getX(), ptRef2.getY() - ptRef1.getY());        
        nSlope = ptDiff.getY() / ptDiff.getX();        
        ptResult.setLocation(ptRef1.getX() + ((nYValue - ptRef1.getY()) / nSlope), nYValue);
        return ptResult;                              
    }
    
    // Calculate a value that represent the importance of the distance
    // between to point for a givem maximmun value
    private double CalculateDistanceImportanceFactor(double nDistance, double nMaxDistance)
    {
        return (nDistance <= nMaxDistance) ? ((nMaxDistance - nDistance) / nMaxDistance) : 0;
    }
    
    // Try to predict where the ball will go on the next step
    private boolean PredictBallPosition(Point ptPosition)
    {
        // If we have only two valid balls position, then
        // apply the direction vector defined by the both
        // to the current ball position
        if (nValidBalls >= 2) {
            int        nCurIdx;
            int        nPrvIdx;
            nCurIdx = nBallStartIdx;
            nPrvIdx = (nBallStartIdx + 2) % 3;
            ptPosition.setLocation((2.0 * ptBalls[nCurIdx].getX()) - ptBalls[nPrvIdx].getX(),
                                   (2.0 * ptBalls[nCurIdx].getY()) - ptBalls[nPrvIdx].getY());
            
            return true;
        }
        
        // Nothing could be predicted
        return false;
    }

    // Creates a kick command calculating the best 
    // destination where the ball must fall
    private ComandoGolpearBola KickOff(int nPlayer1, int nPlayer2, int nPower){
        double dX = ptTeamPlayers1[nPlayer2].getX() - ptTeamPlayers1[nPlayer1].getX();
        double dY = ptTeamPlayers1[nPlayer2].getY() - ptTeamPlayers1[nPlayer1].getY();
        if ((dX != 0) && (dY != 0)) { 
            double nLength;
            nLength  = VectorLength(dX, dY);
            dX = dX / nLength;
            dY = dY / nLength;
            int nP1 =Math.min(nPlayers1[nPlayer1][2], nPower);
            double d = 50.0 * (double) nP1 /(double) nPlayers1[nPlayer1][2];
            double dd=d,ddx=dX,ddy=dY;
            int ff;
            do{
                dX=(int)((double)ddx*dd);
                dY=(int)((double)ddy*dd);
                nLength  = VectorLength(dX, dY);
                ff= nPlayers1[nPlayer1][2] * (int) nLength / 52;
                dd++;
            } while (ff < nP1);
            return new ComandoGolpearBola(nPlayer1,
                                          ptTeamPlayers1[nPlayer1].getX() + dX,
                                          ptTeamPlayers1[nPlayer1].getY() + dY);
        } else
            return new ComandoGolpearBola(nPlayer1,
                                          ptTeamPlayers1[nPlayer1].getX(),
                                          ptTeamPlayers1[nPlayer1].getY());
    }
    
    
    public String getNombre()
    {
        return "FCSilgur";
    }
    
    public Color getColor1()
    {
        return new Color(64,140,128);
    }
    
    public Color getColor2()
    {
        return new Color(128,140,64);
    }
    
    public Color getColorNumero()
    {
        return Color.white;
    }
    
    public int getFuerza(int n)
    {
        return nPlayers1[n][2];
    }
    
    public int getXInicial(int n)
    {
        return nPlayers1[n][0];
    }
    
    public int getYInicial(int n)
    {
        return nPlayers1[n][1];
    }
    
    public Vector<Comando> getComandos(SituacionJugadores playerSituation)
    {
        // Declare the commands vector
        Vector<Comando> commands=new Vector();
        
        // Obtain the players positions
        ptTeamPlayers1 = playerSituation.getMisJugadores();
        ptTeamPlayers2 = playerSituation.getContrario();
        
        // Obtain the ball position
        nBallStartIdx = (nBallStartIdx + 1) % 3;
        ptBalls[nBallStartIdx] = playerSituation.getBola();
        nValidBalls = nValidBalls >= 3 ? 3 : nValidBalls + 1;

        // Match result calculation
        if (nValidBalls >= 2) {
            int     nCurIdx, nPrvIdx;
            nCurIdx = nBallStartIdx;
            nPrvIdx = (nBallStartIdx + 2) % 3;
            if ((ptBalls[nPrvIdx].getX() > 135) && (ptBalls[nCurIdx].getX() < 40)) {
                nMatchResult[0]++;
                nValidBalls = 1;
            }
            if ((ptBalls[nPrvIdx].getX() < -135) && (ptBalls[nCurIdx].getX() > -40)) {
                nMatchResult[1]++;
                nValidBalls = 1;
            }
        }
        
        // Apply the tactics to each players row
        for (int n = 0; n < 11; n++) {
            switch (nPlayers1[n][3]) {
            case 0: ApplyGoalKeeperTactic(playerSituation, commands, n);   break;
            case 1: ApplyDefenseTactic1(playerSituation, commands, n);     break;
            case 2: ApplyDefenseTactic2(playerSituation, commands, n);     break;
            case 3: ApplyAttackTactic1(playerSituation, commands, n);      break;
            case 4: ApplyAttackTactic2(playerSituation, commands, n);      break;
            }
        }
        return commands;
    }
    
    private void ApplyGoalKeeperTactic(SituacionJugadores playerSituation, Vector<Comando> commands, int nPlayer)
    {
        // If the current player is the nearest one to the ball, go for it
        if (playerSituation.getMasCercanoDeBola() == nPlayer) {
            Point       ptPrediction = new Point();
            if (PredictBallPosition(ptPrediction)) {
                commands.add(new ComandoIrA(nPlayer, ptPrediction.getX(), ptPrediction.getY()));
            }
            else {
                commands.add(new ComandoIrA(nPlayer, ptBalls[nBallStartIdx].getX(), ptBalls[nBallStartIdx].getY()));
            }
        }
        else if (ptBalls[nBallStartIdx].getX() <= 40) {        // Attack is coming
            Point   ptBall = new Point();
            if (!PredictBallPosition(ptBall))
                ptBall = ptBalls[nBallStartIdx];
            // Set the X position for the goal keeper
            double      nXFactor;
            double      nXPos;
            double      nYPos;
            nXFactor = (120.0 + ptBall.getX()) / 200.0;
            nXPos = 15.0 * nXFactor - 148;            
            nYPos = ptBall.getY();
            if (nYPos < -30)
                nYPos = -30;
            else if (nYPos > 30)
                nYPos = 30;
            commands.add(new ComandoIrA(nPlayer, nXPos, nYPos));
        }
        else {
            double      nYPos;
            nYPos = ptBalls[nBallStartIdx].getY();
            if (nYPos < -25)
                nYPos = -25;
            else if (nYPos > 25)
                nYPos = 25;
            commands.add(new ComandoIrA(nPlayer, nPlayers1[nPlayer][0], nYPos));
        }
        
        // Also try to make a goal from its position
        commands.add(new ComandoTiroAPuerta(nPlayer, 0));
    }
    
    private void ApplyDefenseTactic1(SituacionJugadores playerSituation, Vector<Comando> commands, int nPlayer)
    {
        // Comprovar si tiene un jugador asociado
        int nLinkedPlayer;
        nLinkedPlayer = nPlayers1[nPlayer][4];
        
        // If the current player is the nearest one to the ball, go for it
        if (playerSituation.getMasCercanoDeBola() == nPlayer) {
            Point       ptPrediction = new Point();
            if (PredictBallPosition(ptPrediction))
                commands.add(new ComandoIrA(nPlayer, ptPrediction.getX(), ptPrediction.getY()));
            else
                commands.add(new ComandoIrA(nPlayer, ptBalls[nBallStartIdx].getX(), ptBalls[nBallStartIdx].getY()));
            nPlayers1[nPlayer][4] = -1;
            if (nLinkedPlayer != -1)
                nPlayers2[nLinkedPlayer] = -1;
        }
        // If the player is far away from the ball, try to find its linked player
        else if (Distance(nPlayer, ptBalls[nBallStartIdx], false) > 30.0) {
            if (nLinkedPlayer == -1) {
                // Look for the nearest opponent player
                int nCurIdx = -1;
                int nExclude[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
                do {
                    int nNearest[] = playerSituation.getInfoMasCercano(ptTeamPlayers2, ptTeamPlayers1[nPlayer], nExclude, false);
                    nCurIdx++;
                    nLinkedPlayer = nNearest[0];
                    nExclude[nCurIdx] = nNearest[0];
                } while ((nPlayers2[nLinkedPlayer] != -1) && (nCurIdx < 11));
                
                if ((Distance(nPlayer, nLinkedPlayer, true) <= 30.0) && (ptTeamPlayers2[nLinkedPlayer].getX() < -90)) {
                    commands.add(new ComandoIrA(nPlayer, ptTeamPlayers2[nLinkedPlayer].getX(), ptTeamPlayers2[nLinkedPlayer].getY()));
            
                    // Update the linked player
                    nPlayers1[nPlayer][4] = nLinkedPlayer;
                    nPlayers2[nLinkedPlayer] = nPlayer;
                }
                else
                    commands.add(new ComandoIrA(nPlayer, nPlayers1[nPlayer][0], nPlayers1[nPlayer][1]));
            }
            else {
                // If the linked player is far away or defensive player is 
                // going byond its limits, move it to the base position
                if ((Distance(nPlayer, nLinkedPlayer, true) > 30.0) || (ptTeamPlayers1[nPlayer].getX() > -90)) {
                    commands.add(new ComandoIrA(nPlayer, nPlayers1[nPlayer][0], nPlayers1[nPlayer][1]));
                    nPlayers1[nPlayer][4] = -1;
                    if (nLinkedPlayer != -1)
                        nPlayers2[nLinkedPlayer] = -1;
                }
                else
                    commands.add(new ComandoIrA(nPlayer, ptTeamPlayers2[nLinkedPlayer].getX(), ptTeamPlayers2[nLinkedPlayer].getY()));
            }
        }
        else {
            // If ball and player isn't beyond its action area, goes to the ball
            if ((ptTeamPlayers1[nPlayer].getX() < -90) && (ptBalls[nBallStartIdx].getX() < -90)) {
                Point       ptPrediction = new Point();
                if (PredictBallPosition(ptPrediction))
                    commands.add(new ComandoIrA(nPlayer, ptPrediction.getX(), ptPrediction.getY()));
                else
                    commands.add(new ComandoIrA(nPlayer, ptBalls[nBallStartIdx].getX(), ptBalls[nBallStartIdx].getY()));
            }
            else
                commands.add(new ComandoIrA(nPlayer, nPlayers1[nPlayer][0], nPlayers1[nPlayer][1]));
            nPlayers1[nPlayer][4] = -1;
            if (nLinkedPlayer != -1)
                nPlayers2[nLinkedPlayer] = -1;
        }
        
        // If the player can kick the ball, it should be
        // towards the nearest second row player
        int     nNearest = -1;
        double  nMinDistance = 500;
        for (int n = 0; n < 11; n++) {
            // Do nothing if the player is not from a least the second row
            if (nPlayers1[n][3] < 2)
                continue;
            
            // Obtener la distancia entre jugadores
            double      nDistance;
            nDistance = Distance(nPlayer, n, false);
            if (nMinDistance > nDistance) {
                nMinDistance = nDistance;
                nNearest = n;
            }
        }
        
        // If someone has been found, do the pass
        if (nNearest  != -1) {
            double      nDistance;
            nDistance = Distance(nNearest, ptBalls[nBallStartIdx], false);
            int     nPower;
            nPower = ((nDistance > 90) ? 7 : ((nDistance > 40) ? 6 : 4));
            commands.add(KickOff(nPlayer, nNearest, nPower));
        }
        else
            commands.add(new ComandoTiroAPuerta(nPlayer, 4));
    }

    private void ApplyDefenseTactic2(SituacionJugadores playerSituation, Vector<Comando> commands, int nPlayer)
    {
        // If the current player is the nearest one to the ball, go for it
        if (playerSituation.getMasCercanoDeBola() == nPlayer) {
            Point       ptPrediction = new Point();
            if (PredictBallPosition(ptPrediction))
                commands.add(new ComandoIrA(nPlayer, ptPrediction.getX(), ptPrediction.getY()));
            else
                commands.add(new ComandoIrA(nPlayer, ptBalls[nBallStartIdx].getX(), ptBalls[nBallStartIdx].getY()));
            if ((ptBalls[nBallStartIdx].getX() % 2) == 0)
                commands.add(new ComandoTiroAPuerta(nPlayer, 3));
            else
                commands.add(KickOff(nPlayer, nPlayer + 1, 5));
        }
        else {
            if (Distance(nPlayer, ptBalls[nBallStartIdx], false) > 60)
                commands.add(new ComandoIrA(nPlayer, nPlayers1[nPlayer][0], nPlayers1[nPlayer][1]));
            else {
                Point       ptPrediction = new Point();
                if (PredictBallPosition(ptPrediction))
                    commands.add(new ComandoIrA(nPlayer, ptPrediction.getX(), ptPrediction.getY()));
                else
                    commands.add(new ComandoIrA(nPlayer, ptBalls[nBallStartIdx].getX(), ptBalls[nBallStartIdx].getY()));
            }
            if ((ptBalls[nBallStartIdx].getX() % 2) == 0)
                commands.add(new ComandoTiroAPuerta(nPlayer, 3));
            else
                commands.add(KickOff(nPlayer, nPlayer + 1, 5));
        }
    }

    private void ApplyAttackTactic1(SituacionJugadores playerSituation, Vector<Comando> commands, int nPlayer)
    {
        // If the current player is the nearest one to the ball, go for it
        if (playerSituation.getMasCercanoDeBola() == nPlayer) {
            Point       ptPrediction = new Point();
            if (PredictBallPosition(ptPrediction))
                commands.add(new ComandoIrA(nPlayer, ptPrediction.getX(), ptPrediction.getY()));
            else
                commands.add(new ComandoIrA(nPlayer, ptBalls[nBallStartIdx].getX(), ptBalls[nBallStartIdx].getY()));
            if ((ptBalls[nBallStartIdx].getX() % 2) == 0)
                commands.add(new ComandoTiroAPuerta(nPlayer, 2));
            else
                commands.add(KickOff(nPlayer, nPlayer + 1, 4));
        }
        else {
            if (Distance(nPlayer, ptBalls[nBallStartIdx], false) > 80)
                commands.add(new ComandoIrA(nPlayer, nPlayers1[nPlayer][0], nPlayers1[nPlayer][1]));
            else {
                Point       ptPrediction = new Point();
                if (PredictBallPosition(ptPrediction))
                    commands.add(new ComandoIrA(nPlayer, ptPrediction.getX(), ptPrediction.getY()));
                else
                    commands.add(new ComandoIrA(nPlayer, ptBalls[nBallStartIdx].getX(), ptBalls[nBallStartIdx].getY()));
            }            
            if ((ptBalls[nBallStartIdx].getX() % 2) == 0)
                commands.add(new ComandoTiroAPuerta(nPlayer, 2));
            else
                commands.add(KickOff(nPlayer, nPlayer + 1, 4));
        }
    }

    private void ApplyAttackTactic2(SituacionJugadores playerSituation, Vector<Comando> commands, int nPlayer)
    {
        // If the current player is the nearest one to the ball, go for it
        if (playerSituation.getMasCercanoDeBola() == nPlayer) {
            Point       ptPrediction = new Point();
            if (PredictBallPosition(ptPrediction))
                commands.add(new ComandoIrA(nPlayer, ptPrediction.getX(), ptPrediction.getY()));
            else
                commands.add(new ComandoIrA(nPlayer, ptBalls[nBallStartIdx].getX(), ptBalls[nBallStartIdx].getY()));
            commands.add(new ComandoTiroAPuerta(nPlayer, 1));
        }
        else {
            if (Distance(nPlayer, ptBalls[nBallStartIdx], false) > 90)
                commands.add(new ComandoIrA(nPlayer, nPlayers1[nPlayer][0], nPlayers1[nPlayer][1]));
            else {
                Point       ptPrediction = new Point();
                if (PredictBallPosition(ptPrediction))
                    commands.add(new ComandoIrA(nPlayer, ptPrediction.getX(), ptPrediction.getY()));
                else
                    commands.add(new ComandoIrA(nPlayer, ptBalls[nBallStartIdx].getX(), ptBalls[nBallStartIdx].getY()));
            }
            commands.add(new ComandoTiroAPuerta(nPlayer, 1));
        }
    }
}
